﻿using Opc.Ua;
using Opc.Ua.Client;
using OpcUA.Model;
using System.Collections;

namespace OpcUA
{
    /// <summary>
    /// OPC UA Client with examples of basic functionality.
    /// </summary>
    public class UAClient
    {
        private bool m_IsConnected;                       //是否已经连接过
        private int m_reconnectPeriod = 10;               // 重连状态

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the UAClient class.
        /// </summary>
        public UAClient(ApplicationConfiguration configuration, IOutput output, Action<IList, IList> validateResponse)
        {
            dic_subscriptions = new Dictionary<string, Subscription>();
            m_validateResponse = validateResponse;
            m_output = output;
            m_configuration = configuration;
            m_configuration.CertificateValidator.CertificateValidation += CertificateValidation;
        }

        #endregion Constructors

        #region Public Properties

        /// <summary>
        /// Gets or sets the server URL.
        /// </summary>
        public string ServerUrl { get; set; } = "";

        /// <summary>
        /// Gets the client session.
        /// </summary>
        public Session Session => m_session;

        /// <summary>
        /// The user identity to use when creating the session.
        /// </summary>
        public IUserIdentity UserIdentity { get; set; }

        #endregion Public Properties

        #region Public Methods

        //        foreach (ReferenceDescription result in browseResults)
        //        {
        //            m_output.WriteLine("  NodeID={0}, DisplayName = {1}, NodeClass = {2}",result.NodeId.ToString(), result.DisplayName.Text, result.NodeClass);
        //        }
        //    }
        //    catch (Exception ex)
        //    {
        //        // Log Error
        //        m_output.WriteLine($"Browse Error : {ex.Message}.");
        //    }
        //}
        private List<ReferenceDescriptionCollection> ListCollection = new List<ReferenceDescriptionCollection>();

        //        // Display the results
        //        m_output.WriteLine("Browse returned {0} results:", browseResults.Count);
        public void Browse()
        {
            if (m_session == null || m_session.Connected == false)
            {
                UpdateStatus(false, DateTime.UtcNow, "Session not connected!");
                return;
            }

            try
            {
                GetAllReferenceDescriptionCollection(ObjectIds.ObjectsFolder);
                foreach (ReferenceDescriptionCollection collection in ListCollection)
                {
                    foreach (ReferenceDescription result in collection)
                    {
                        UpdateStatus(true, DateTime.UtcNow, "NodeID={0}, DisplayName = {1}, NodeClass = {2}", result.NodeId.ToString(), result.DisplayName.Text, result.NodeClass);
                    }
                }
            }
            catch (Exception ex)
            {
                // Log Error
                UpdateStatus(false, DateTime.UtcNow, "Browse Error : {0}.", ex.Message);
            }
        }

        /// <summary>
        /// Call UA method
        /// </summary>
        public void CallMethod()
        {
            if (m_session == null || m_session.Connected == false)
            {
                m_output.WriteLine("Session not connected!");
                return;
            }

            try
            {
                // Define the UA Method to call
                // Parent node - Objects\CTT\Methods
                // Method node - Objects\CTT\Methods\Add
                NodeId objectId = new NodeId("ns=2;s=Methods");
                NodeId methodId = new NodeId("ns=2;s=Methods_Add");

                // Define the method parameters
                // Input argument requires a Float and an UInt32 value
                object[] inputArguments = new object[] { (float)10.5, (uint)10 };
                IList<object> outputArguments = null;

                // Invoke Call service
                m_output.WriteLine("Calling UAMethod for node {0} ...", methodId);
                outputArguments = m_session.Call(objectId, methodId, inputArguments);

                // Display results
                m_output.WriteLine("Method call returned {0} output argument(s):", outputArguments.Count);

                foreach (var outputArgument in outputArguments)
                {
                    m_output.WriteLine("     OutputValue = {0}", outputArgument.ToString());
                }
            }
            catch (Exception ex)
            {
                m_output.WriteLine("Method call error: {0}", ex.Message);
            }
        }

        /// <summary>
        /// 创建与UA服务器的会话
        /// </summary>
        public async Task<bool> ConnectAsync()
        {
            try
            {
                if (m_session != null && m_session.Connected == true)
                {
                    UpdateStatus(true, DateTime.UtcNow, "Session already connected!");
                }
                else
                {
                    UpdateStatus(true, DateTime.UtcNow, "Connecting...");

                    // Get the endpoint by connecting to server's discovery endpoint.
                    // Try to find the first endopint without security.
                    EndpointDescription endpointDescription = CoreClientUtils.SelectEndpoint(ServerUrl, false);

                    EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(m_configuration);
                    ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);

                    // Create the session
                    Session session = await Session.Create(
                        m_configuration,
                        endpoint,
                        false,
                        false,
                        m_configuration.ApplicationName,
                        30 * 60 * 1000,
                        UserIdentity,
                        null
                    );
                    // set up keep alive callback.
                    // Assign the created session
                    if (session != null && session.Connected)
                    {
                        m_IsConnected = true;

                        // raise an event.
                        DoConnectComplete(null);
                        m_session = session;
                        m_session.KeepAlive += new KeepAliveEventHandler(Session_KeepAlive);
                    }

                    // Session created successfully.
                    UpdateStatus(true, DateTime.UtcNow, "New Session Created with SessionName = {0}", m_session.SessionName);
                }

                return true;
            }
            catch (Exception ex)
            {
                // Log Error
                UpdateStatus(false, DateTime.UtcNow, "Create Session Error : {0}", ex.Message);
                return false;
            }
        }

        /// <summary>
        /// Disconnects the session.
        /// </summary>
        public void Disconnect()
        {
            try
            {
                if (m_session != null)
                {
                    // stop any reconnect operation.
                    if (m_reConnectHandler != null)
                    {
                        m_reConnectHandler.Dispose();
                        m_reConnectHandler = null;
                    }
                    m_session.Close();
                    m_session.Dispose();
                    m_session = null;

                    // Log Session Disconnected event
                    UpdateStatus(false, DateTime.UtcNow, "Session Disconnected");
                }
                else
                {
                    UpdateStatus(false, DateTime.UtcNow, "Session not created!");
                }
                // raise an event.
                DoConnectComplete(null);
            }
            catch (Exception ex)
            {
                // Log Error
                UpdateStatus(false, DateTime.UtcNow, "Disconnect Error : {0}", ex.Message);
            }
        }

        /// <summary>
        /// 获取父节点下的子节点
        /// </summary>
        /// <param name="sourceId"></param>
        /// <returns></returns>
        public ReferenceDescriptionCollection GetReferenceDescriptionCollection(NodeId sourceId)
        {
            //UpdateStatus(true, DateTime.UtcNow, "Browsing {0} node...", sourceId.ToString());
            TaskCompletionSource<ReferenceDescriptionCollection> task = new TaskCompletionSource<ReferenceDescriptionCollection>();

            // find all of the components of the node.
            BrowseDescription nodeToBrowse1 = new BrowseDescription();
            nodeToBrowse1.NodeId = sourceId;
            nodeToBrowse1.BrowseDirection = BrowseDirection.Forward;
            nodeToBrowse1.ReferenceTypeId = ReferenceTypeIds.Aggregates;
            nodeToBrowse1.IncludeSubtypes = true;
            nodeToBrowse1.NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable | NodeClass.Method | NodeClass.ReferenceType | NodeClass.ObjectType | NodeClass.View | NodeClass.VariableType | NodeClass.DataType);
            nodeToBrowse1.ResultMask = (uint)BrowseResultMask.All;
            // find all nodes organized by the node.
            BrowseDescription nodeToBrowse2 = new BrowseDescription();
            nodeToBrowse2.NodeId = sourceId;
            nodeToBrowse2.BrowseDirection = BrowseDirection.Forward;
            nodeToBrowse2.ReferenceTypeId = ReferenceTypeIds.Organizes;
            nodeToBrowse2.IncludeSubtypes = true;
            nodeToBrowse2.NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable | NodeClass.Method | NodeClass.View | NodeClass.ReferenceType | NodeClass.ObjectType | NodeClass.VariableType | NodeClass.DataType);
            nodeToBrowse2.ResultMask = (uint)BrowseResultMask.All;
            BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection();
            nodesToBrowse.Add(nodeToBrowse1);
            nodesToBrowse.Add(nodeToBrowse2);

            // fetch references from the server.
            ReferenceDescriptionCollection references = FormUtils.Browse(m_session, nodesToBrowse, false);

            return references;
        }

        /// <summary>
        /// Read a list of nodes from Server
        /// </summary>
        public void ReadNodes()
        {
            if (m_session == null || m_session.Connected == false)
            {
                UpdateStatus(false, DateTime.UtcNow, "Session not connected!");
                return;
            }

            try
            {
                #region Read a node by calling the Read Service

                // build a list of nodes to be read
                ReadValueIdCollection nodesToRead = new ReadValueIdCollection()
                {
                    // Value of ServerStatus
                    new ReadValueId() { NodeId = Variables.Server_ServerStatus, AttributeId = Attributes.Value },
                    // BrowseName of ServerStatus_StartTime
                    new ReadValueId() { NodeId = Variables.Server_ServerStatus_StartTime, AttributeId = Attributes.BrowseName },
                    // Value of ServerStatus_StartTime
                    new ReadValueId() { NodeId = Variables.Server_ServerStatus_StartTime, AttributeId = Attributes.Value }
                };

                // Read the node attributes
                UpdateStatus(true, DateTime.UtcNow, "Reading nodes...");

                // Call Read Service
                m_session.Read(
                    null,
                    0,
                    TimestampsToReturn.Both,
                    nodesToRead,
                    out DataValueCollection resultsValues,
                    out DiagnosticInfoCollection diagnosticInfos);

                // Validate the results
                m_validateResponse(resultsValues, nodesToRead);

                // Display the results.
                foreach (DataValue result in resultsValues)
                {
                    UpdateStatus(true, DateTime.UtcNow, "Read Value = {0} , StatusCode = {1}", result.Value, result.StatusCode);
                }

                #endregion Read a node by calling the Read Service

                #region Read the Value attribute of a node by calling the Session.ReadValue method

                // Read Server NamespaceArray
                UpdateStatus(true, DateTime.UtcNow, "Reading Value of NamespaceArray node...");
                DataValue namespaceArray = m_session.ReadValue(Variables.Server_NamespaceArray);
                // Display the result
                UpdateStatus(true, DateTime.UtcNow, "NamespaceArray Value = {0}", namespaceArray);

                #endregion Read the Value attribute of a node by calling the Session.ReadValue method
            }
            catch (Exception ex)
            {
                // Log Error
                UpdateStatus(false, DateTime.UtcNow, "Read Nodes Error : {0}.", ex.Message);
            }
        }

        /// <summary>
        /// Browse Server nodes
        /// </summary>
        //public void Browse()
        //{
        //    if (m_session == null || m_session.Connected == false)
        //    {
        //        m_output.WriteLine("Session not connected!");
        //        return;
        //    }

        //    try
        //    {
        //        // Create a Browser object
        //        Browser browser = new Browser(m_session);

        //        // Set browse parameters
        //        browser.BrowseDirection = BrowseDirection.Forward;
        //        browser.NodeClassMask = (int)(NodeClass.Object | NodeClass.Variable | NodeClass.Method | NodeClass.View | NodeClass.ReferenceType | NodeClass.ObjectType | NodeClass.VariableType | NodeClass.DataType);
        //        browser.ReferenceTypeId = ReferenceTypeIds.Organizes;

        //        //NodeId nodeToBrowse = ObjectIds.Server;
        //        NodeId nodeToBrowse = ObjectIds.ObjectsFolder;

        /// <summary>
        ///读取5个属性值 0:NodeClass  1:Value  2:AccessLevel  3:DisplayName  4:Description
        /// </summary>
        /// <param name="nodeIds"></param>
        /// <returns></returns>
        public DataValue[] ReadOneNodeFiveAttributes(List<NodeId> nodeIds)
        {
            ReadValueIdCollection nodesToRead = new ReadValueIdCollection();
            foreach (var nodeId in nodeIds)
            {
                NodeId sourceId = nodeId;
                nodesToRead.Add(new ReadValueId()
                {
                    NodeId = sourceId,
                    AttributeId = Attributes.NodeClass
                });
                nodesToRead.Add(new ReadValueId()
                {
                    NodeId = sourceId,
                    AttributeId = Attributes.Value
                });
                nodesToRead.Add(new ReadValueId()
                {
                    NodeId = sourceId,
                    AttributeId = Attributes.AccessLevel
                });
                nodesToRead.Add(new ReadValueId()
                {
                    NodeId = sourceId,
                    AttributeId = Attributes.DisplayName
                });

                nodesToRead.Add(new ReadValueId()
                {
                    NodeId = sourceId,
                    AttributeId = Attributes.Description
                });
            }

            // read all values.
            m_session.Read(
                null,
                0,
                TimestampsToReturn.Neither,
                nodesToRead,
                out DataValueCollection results,
                out DiagnosticInfoCollection diagnosticInfos);

            ClientBase.ValidateResponse(results, nodesToRead);
            ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToRead);

            return results.ToArray();
        }

        /// <summary>
        ///读取1个属性值 0:NodeClass
        /// </summary>
        /// <param name="nodeIds"></param>
        /// <returns></returns>
        public DataValue[] ReadOneNodeTwoAttributes(NodeId nodeId)
        {
            ReadValueIdCollection nodesToRead = new ReadValueIdCollection();
            NodeId sourceId = nodeId;
            nodesToRead.Add(new ReadValueId()
            {
                NodeId = sourceId,
                AttributeId = Attributes.NodeClass
            });
            nodesToRead.Add(new ReadValueId()
            {
                NodeId = sourceId,
                AttributeId = Attributes.Value
            });
            // read all values.
            m_session.Read(
                null,
                0,
                TimestampsToReturn.Neither,
                nodesToRead,
                out DataValueCollection results,
                out DiagnosticInfoCollection diagnosticInfos);

            ClientBase.ValidateResponse(results, nodesToRead);
            ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToRead);
            return results.ToArray();
        }

        /// <summary>
        /// Create Subscription and MonitoredItems for DataChanges
        /// </summary>
        public void SubscribeToDataChanges()
        {
            if (m_session == null || m_session.Connected == false)
            {
                UpdateStatus(false, DateTime.UtcNow, "Session not connected!");
                return;
            }

            try
            {
                // Create a subscription for receiving data change notifications

                // Define Subscription parameters
                Subscription subscription = new Subscription(m_session.DefaultSubscription);

                subscription.DisplayName = "Console ReferenceClient Subscription";
                subscription.PublishingEnabled = true;
                subscription.PublishingInterval = 1000;

                m_session.AddSubscription(subscription);

                // Create the subscription on Server side
                subscription.Create();
                UpdateStatus(true, DateTime.UtcNow, "New Subscription created with SubscriptionId = {0}.", subscription.Id);

                // Create MonitoredItems for data changes

                MonitoredItem intMonitoredItem = new MonitoredItem(subscription.DefaultItem);
                // Int32 Node - Objects\CTT\Scalar\Simulation\Int32
                intMonitoredItem.StartNodeId = new NodeId("ns=2;s=Channel1.Device1.Tag1");
                intMonitoredItem.AttributeId = Attributes.Value;
                intMonitoredItem.DisplayName = "Int32 Variable";
                intMonitoredItem.SamplingInterval = 1000;
                intMonitoredItem.Notification += OnMonitoredItemNotification;

                subscription.AddItem(intMonitoredItem);

                MonitoredItem floatMonitoredItem = new MonitoredItem(subscription.DefaultItem);
                // Float Node - Objects\CTT\Scalar\Simulation\Float
                floatMonitoredItem.StartNodeId = new NodeId("ns=2;s=Channel1.Device1.Tag4");
                floatMonitoredItem.AttributeId = Attributes.Value;
                floatMonitoredItem.DisplayName = "String Variable";
                floatMonitoredItem.SamplingInterval = 1000;
                floatMonitoredItem.Notification += OnMonitoredItemNotification;

                subscription.AddItem(floatMonitoredItem);

                MonitoredItem stringMonitoredItem = new MonitoredItem(subscription.DefaultItem);
                // String Node - Objects\CTT\Scalar\Simulation\String
                stringMonitoredItem.StartNodeId = new NodeId("ns=2;s=Channel1.Device1.Tag3");
                stringMonitoredItem.AttributeId = Attributes.Value;
                stringMonitoredItem.DisplayName = "Int32 Variable";
                stringMonitoredItem.SamplingInterval = 1000;
                stringMonitoredItem.Notification += OnMonitoredItemNotification;

                subscription.AddItem(stringMonitoredItem);

                // Create the monitored items on Server side
                subscription.ApplyChanges();
                m_output.WriteLine("MonitoredItems created for SubscriptionId = {0}.", subscription.Id);
            }
            catch (Exception ex)
            {
                UpdateStatus(false, DateTime.UtcNow, "Subscribe error: {0}", ex.Message);
            }
        }

        //        // Call Browse service
        //        m_output.WriteLine("Browsing {0} node...", nodeToBrowse);
        //        ReferenceDescriptionCollection browseResults = browser.Browse(nodeToBrowse);
        /// <summary>
        ///
        /// </summary>
        /// <param name="sourceId"></param>
        private void GetAllReferenceDescriptionCollection(NodeId sourceId)
        {
            if (m_session == null || m_session.Connected == false)
            {
                UpdateStatus(false, DateTime.UtcNow, "Session not connected!");
                return;
            }

            try
            {
                //UpdateStatus(true, DateTime.UtcNow, "Browsing {0} node...", sourceId.ToString());
                TaskCompletionSource<ReferenceDescriptionCollection> task = new TaskCompletionSource<ReferenceDescriptionCollection>();

                // find all of the components of the node.
                BrowseDescription nodeToBrowse1 = new BrowseDescription();

                nodeToBrowse1.NodeId = sourceId;
                nodeToBrowse1.BrowseDirection = BrowseDirection.Forward;
                nodeToBrowse1.ReferenceTypeId = ReferenceTypeIds.Aggregates;
                nodeToBrowse1.IncludeSubtypes = true;
                nodeToBrowse1.NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable | NodeClass.Method | NodeClass.ReferenceType | NodeClass.ObjectType | NodeClass.View | NodeClass.VariableType | NodeClass.DataType);
                nodeToBrowse1.ResultMask = (uint)BrowseResultMask.All;

                // find all nodes organized by the node.
                BrowseDescription nodeToBrowse2 = new BrowseDescription();

                nodeToBrowse2.NodeId = sourceId;
                nodeToBrowse2.BrowseDirection = BrowseDirection.Forward;
                nodeToBrowse2.ReferenceTypeId = ReferenceTypeIds.Organizes;
                nodeToBrowse2.IncludeSubtypes = true;
                nodeToBrowse2.NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable | NodeClass.Method | NodeClass.View | NodeClass.ReferenceType | NodeClass.ObjectType | NodeClass.VariableType | NodeClass.DataType);
                nodeToBrowse2.ResultMask = (uint)BrowseResultMask.All;
                BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection();
                nodesToBrowse.Add(nodeToBrowse1);
                nodesToBrowse.Add(nodeToBrowse2);
                // fetch references from the server.
                ReferenceDescriptionCollection references = FormUtils.Browse(m_session, nodesToBrowse, false);
                if (references.Count > 0)
                {
                    ListCollection.Add(references);
                    foreach (ReferenceDescription item in references)
                    {
                        GetReferenceDescriptionCollection((NodeId)item.NodeId);
                    }
                }
            }
            catch (Exception ex)
            {
                // Log Error
                UpdateStatus(false, DateTime.UtcNow, "Browse Error : {0}.", ex.Message);
            }
        }

        #region LogOut Setting

        /// <summary>
        /// 设置OPC客户端的日志输出
        /// </summary>
        /// <param name="filePath">完整的文件路径</param>
        /// <param name="deleteExisting">是否删除原文件</param>
        public void SetLogPathName(string filePath, bool deleteExisting)
        {
            Utils.SetTraceLog(filePath, deleteExisting);
            Utils.SetTraceMask(515);
        }

        #endregion LogOut Setting

        #region Public Members

        /// <summary>
        /// Raised after successfully connecting to or disconnecing from a server.
        /// </summary>
        public event EventHandler ConnectComplete
        {
            add { m_ConnectComplete += value; }
            remove { m_ConnectComplete -= value; }
        }

        /// <summary>
        /// Raised when a good keep alive from the server arrives.
        /// </summary>
        public event EventHandler KeepAliveComplete
        {
            add { m_KeepAliveComplete += value; }
            remove { m_KeepAliveComplete -= value; }
        }

        /// <summary>
        /// Raised after the client status change
        /// </summary>
        public event EventHandler<OpcUaStatusEventArgs> OpcStatusChange
        {
            add { m_OpcStatusChange += value; }
            remove { m_OpcStatusChange -= value; }
        }

        /// <summary>
        /// Raised when a reconnect operation completes.
        /// </summary>
        public event EventHandler ReconnectComplete
        {
            add { m_ReconnectComplete += value; }
            remove { m_ReconnectComplete -= value; }
        }

        /// <summary>
        /// Raised when a reconnect operation starts.
        /// </summary>
        public event EventHandler ReconnectStarting
        {
            add { m_ReconnectStarting += value; }
            remove { m_ReconnectStarting -= value; }
        }

        /// <summary>
        /// 配置信息
        /// </summary>
        public ApplicationConfiguration AppConfig => m_configuration;

        /// <summary>
        /// Indicate the connect status
        /// </summary>
        public bool Connected
        {
            get { return m_IsConnected; }
        }

        /// <summary>
        /// a name of application name show on server
        /// </summary>
        public string OpcUaName { get; set; } = "Opc Ua Helper";

        /// <summary>
        /// The number of seconds between reconnect attempts (0 means reconnect is disabled).
        /// </summary>
        public int ReconnectPeriod
        {
            get { return m_reconnectPeriod; }
            set { m_reconnectPeriod = value; }
        }

        #endregion Public Members

        #region Node Write/Read Support

        /// <summary>
        /// 从服务器读取一个值节点
        /// </summary>
        /// <param name="nodeId">node id</param>
        /// <returns>DataValue</returns>
        public DataValue ReadNode(NodeId nodeId)
        {
            ReadValueIdCollection nodesToRead = new ReadValueIdCollection
            {
                new ReadValueId( )
                {
                    NodeId = nodeId,
                    AttributeId = Attributes.Value
                }
            };

            // read the current value
            m_session.Read(
                null,
                0,
                TimestampsToReturn.Neither,
                nodesToRead,
                out DataValueCollection results,
                out DiagnosticInfoCollection diagnosticInfos);

            ClientBase.ValidateResponse(results, nodesToRead);
            ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToRead);

            return results[0];
        }

        /// <summary>
        /// 从服务器读取多个值节点
        /// </summary>
        /// <typeparam name="T">type of value</typeparam>
        /// <param name="tag">node id</param>
        /// <returns>实际值</returns>
        public T ReadNode<T>(string tag)
        {
            DataValue dataValue = ReadNode(new NodeId(tag));
            return (T)dataValue.Value;
        }

        /// <summary>
        ///异步读取标签
        /// </summary>
        /// <typeparam name="T">要读取的标签类型</typeparam>
        /// <param name="tag">tag值</param>
        /// <returns>The value retrieved from the OPC</returns>
        public Task<T> ReadNodeAsync<T>(string tag)
        {
            ReadValueIdCollection nodesToRead = new ReadValueIdCollection
            {
                new ReadValueId()
                {
                    NodeId = new NodeId(tag),
                    AttributeId = Attributes.Value
                }
            };

            // Wrap the ReadAsync logic in a TaskCompletionSource, so we can use C# async/await syntax to call it:
            var taskCompletionSource = new TaskCompletionSource<T>();
            m_session.BeginRead(
                requestHeader: null,
                maxAge: 0,
                timestampsToReturn: TimestampsToReturn.Neither,
                nodesToRead: nodesToRead,
                callback: ar =>
                {
                    DataValueCollection results;
                    DiagnosticInfoCollection diag;
                    var response = m_session.EndRead(
                      result: ar,
                      results: out results,
                      diagnosticInfos: out diag);

                    try
                    {
                        CheckReturnValue(response.ServiceResult);
                        CheckReturnValue(results[0].StatusCode);
                        var val = results[0];
                        taskCompletionSource.TrySetResult((T)val.Value);
                    }
                    catch (Exception ex)
                    {
                        taskCompletionSource.TrySetException(ex);
                    }
                },
                asyncState: null);

            return taskCompletionSource.Task;
        }

        /// <summary>
        ///从服务器读取多个值节点
        /// </summary>
        /// <param name="nodeIds">all NodeIds</param>
        /// <returns>all values</returns>
        public List<DataValue> ReadNodes(string[] Tags)
        {
            ReadValueIdCollection nodesToRead = new ReadValueIdCollection();
            for (int i = 0; i < Tags.Length; i++)
            {
                nodesToRead.Add(new ReadValueId()
                {
                    NodeId = new NodeId(Tags[i]),
                    AttributeId = Attributes.Value
                });
            }

            // 读取当前的值
            var data = m_session.Read(
                 null,
                 0,
                 TimestampsToReturn.Neither,
                 nodesToRead,
                 out DataValueCollection results,
                 out DiagnosticInfoCollection diagnosticInfos);

            ClientBase.ValidateResponse(results, nodesToRead);
            ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToRead);

            return results.ToList();
        }

        /// <summary>
        ///从服务器读取多个值节点
        /// </summary>
        /// <param name="tags">所以的节点数组信息</param>
        /// <returns>all values</returns>
        public List<T> ReadNodes<T>(string[] tags)
        {
            List<T> result = new List<T>();
            ReadValueIdCollection nodesToRead = new ReadValueIdCollection();
            for (int i = 0; i < tags.Length; i++)
            {
                nodesToRead.Add(new ReadValueId()
                {
                    NodeId = new NodeId(tags[i]),
                    AttributeId = Attributes.Value
                });
            }

            // 读取当前的值
            m_session.Read(
                null,
                0,
                TimestampsToReturn.Neither,
                nodesToRead,
                out DataValueCollection results,
                out DiagnosticInfoCollection diagnosticInfos);

            ClientBase.ValidateResponse(results, nodesToRead);
            ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToRead);

            foreach (var item in results)
            {
                result.Add((T)item.Value);
            }
            return result;
        }

        /// <summary>
        /// 从服务器读取多个值节点
        /// </summary>
        /// <param name="nodeIds">all NodeIds</param>
        /// <returns>all values</returns>
        public Task<List<DataValue>> ReadNodesAsync(string[] Tags)
        {
            ReadValueIdCollection nodesToRead = new ReadValueIdCollection();
            for (int i = 0; i < Tags.Length; i++)
            {
                nodesToRead.Add(new ReadValueId()
                {
                    NodeId = new NodeId(Tags[i]),
                    AttributeId = Attributes.Value
                });
            }

            var taskCompletionSource = new TaskCompletionSource<List<DataValue>>();
            // 读取当前的值
            m_session.BeginRead(
                null,
                0,
                TimestampsToReturn.Neither,
                nodesToRead,
                callback: ar =>
                {
                    DataValueCollection results;
                    DiagnosticInfoCollection diag;
                    var response = m_session.EndRead(
                      result: ar,
                      results: out results,
                      diagnosticInfos: out diag);

                    try
                    {
                        CheckReturnValue(response.ServiceResult);
                        taskCompletionSource.TrySetResult(results.ToList());
                    }
                    catch (Exception ex)
                    {
                        taskCompletionSource.TrySetException(ex);
                    }
                },
                asyncState: null);

            return taskCompletionSource.Task;
        }

        /// <summary>
        /// read several value nodes from server
        /// </summary>
        /// <param name="tags">all NodeIds</param>
        /// <returns>all values</returns>
        public Task<List<T>> ReadNodesAsync<T>(string[] tags)
        {
            ReadValueIdCollection nodesToRead = new ReadValueIdCollection();
            for (int i = 0; i < tags.Length; i++)
            {
                nodesToRead.Add(new ReadValueId()
                {
                    NodeId = new NodeId(tags[i]),
                    AttributeId = Attributes.Value
                });
            }

            var taskCompletionSource = new TaskCompletionSource<List<T>>();
            // 读取当前的值
            m_session.BeginRead(
                null,
                0,
                TimestampsToReturn.Neither,
                nodesToRead,
                callback: ar =>
                {
                    DataValueCollection results;
                    DiagnosticInfoCollection diag;
                    var response = m_session.EndRead(
                      result: ar,
                      results: out results,
                      diagnosticInfos: out diag);

                    try
                    {
                        CheckReturnValue(response.ServiceResult);
                        List<T> result = new List<T>();
                        foreach (var item in results)
                        {
                            result.Add((T)item.Value);
                        }
                        taskCompletionSource.TrySetResult(result);
                    }
                    catch (Exception ex)
                    {
                        taskCompletionSource.TrySetException(ex);
                    }
                },
                asyncState: null);

            return taskCompletionSource.Task;
        }

        /// <summary>
        /// write a note to server(you should use try catch)
        /// </summary>
        /// <typeparam name="T">The type of tag to write on</typeparam>
        /// <param name="tag">节点名称</param>
        /// <param name="value">值</param>
        /// <returns>if success True,otherwise False</returns>
        public bool WriteNode<T>(string tag, T value)
        {
            WriteValue valueToWrite = new WriteValue()
            {
                NodeId = new NodeId(tag),
                AttributeId = Attributes.Value
            };
            valueToWrite.Value.Value = value;
            valueToWrite.Value.StatusCode = StatusCodes.Good;
            valueToWrite.Value.ServerTimestamp = DateTime.MinValue;
            valueToWrite.Value.SourceTimestamp = DateTime.MinValue;

            WriteValueCollection valuesToWrite = new WriteValueCollection
            {
                valueToWrite
            };

            // 写入当前的值

            m_session.Write(
                null,
                valuesToWrite,
                out StatusCodeCollection results,
                out DiagnosticInfoCollection diagnosticInfos);

            ClientBase.ValidateResponse(results, valuesToWrite);
            ClientBase.ValidateDiagnosticInfos(diagnosticInfos, valuesToWrite);

            if (StatusCode.IsBad(results[0]))
            {
                throw new ServiceResultException(results[0]);
            }

            return !StatusCode.IsBad(results[0]);
        }

        /// <summary>
        /// Write a value on the specified opc tag asynchronously
        /// </summary>
        /// <typeparam name="T">The type of tag to write on</typeparam>
        /// <param name="tag">The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name. E.g: the tag `foo.bar` writes on the tag `bar` on the folder `foo`</param>
        /// <param name="value">The value for the item to write</param>
        public Task<bool> WriteNodeAsync<T>(string tag, T value)
        {
            WriteValue valueToWrite = new WriteValue()
            {
                NodeId = new NodeId(tag),
                AttributeId = Attributes.Value,
            };
            valueToWrite.Value.Value = value;
            valueToWrite.Value.StatusCode = StatusCodes.Good;
            valueToWrite.Value.ServerTimestamp = DateTime.MinValue;
            valueToWrite.Value.SourceTimestamp = DateTime.MinValue;
            WriteValueCollection valuesToWrite = new WriteValueCollection
            {
                valueToWrite
            };

            // Wrap the WriteAsync logic in a TaskCompletionSource, so we can use C# async/await syntax to call it:
            var taskCompletionSource = new TaskCompletionSource<bool>();
            m_session.BeginWrite(
                requestHeader: null,
                nodesToWrite: valuesToWrite,
                callback: ar =>
                {
                    var response = m_session.EndWrite(
                      result: ar,
                      results: out StatusCodeCollection results,
                      diagnosticInfos: out DiagnosticInfoCollection diag);

                    try
                    {
                        ClientBase.ValidateResponse(results, valuesToWrite);
                        ClientBase.ValidateDiagnosticInfos(diag, valuesToWrite);
                        taskCompletionSource.SetResult(StatusCode.IsGood(results[0]));
                    }
                    catch (Exception ex)
                    {
                        taskCompletionSource.TrySetException(ex);
                    }
                },
                asyncState: null);
            return taskCompletionSource.Task;
        }

        /// <summary>
        /// 所有的节点都写入成功，返回<c>True</c>，否则返回<c>False</c>
        /// </summary>
        /// <param name="tags">节点名称数组</param>
        /// <param name="values">节点的值数据</param>
        /// <returns>所有的是否都写入成功</returns>
        public bool WriteNodes(string[] tags, string[] values)
        {
            WriteValueCollection valuesToWrite = new WriteValueCollection();

            for (int i = 0; i < tags.Length; i++)
            {
                if (i < values.Length)
                {
                    WriteValue valueToWrite = new WriteValue()
                    {
                        NodeId = new NodeId(tags[i]),
                        AttributeId = Attributes.Value
                    };
                    DataValue[] dataValues = ReadOneNodeTwoAttributes((NodeId)tags[i]);
                    NodeClass nodeclass = (NodeClass)dataValues[0].WrappedValue.Value;
                    if (nodeclass == NodeClass.Variable)
                    {
                        BuiltInType datatype = dataValues[1].WrappedValue.TypeInfo.BuiltInType;
                        dynamic setvalue = null;
                        if (dataValues[1].WrappedValue.TypeInfo.ValueRank == ValueRanks.Scalar)
                        {
                            setvalue = GetValueFromString(values[i], datatype);
                        }
                        else if (dataValues[1].WrappedValue.TypeInfo.ValueRank == ValueRanks.OneDimension)
                        {
                            setvalue = GetArrayValueFromString(values[i], datatype);
                        }
                        else
                        {
                            throw new Exception("Not supported data type");
                        }
                        valueToWrite.Value.Value = setvalue;
                        valueToWrite.Value.StatusCode = StatusCodes.Good;
                        valueToWrite.Value.ServerTimestamp = DateTime.MinValue;
                        valueToWrite.Value.SourceTimestamp = DateTime.MinValue;
                        valuesToWrite.Add(valueToWrite);
                    }
                }
            }

            // 写入当前的值
            m_session.Write(
                null,
                valuesToWrite,
                out StatusCodeCollection results,
                out DiagnosticInfoCollection diagnosticInfos);

            ClientBase.ValidateResponse(results, valuesToWrite);
            ClientBase.ValidateDiagnosticInfos(diagnosticInfos, valuesToWrite);

            bool result = true;
            foreach (var r in results)
            {
                if (StatusCode.IsBad(r))
                {
                    result = false;
                    break;
                }
            }

            return result;
        }

        private void CheckReturnValue(StatusCode status)
        {
            if (!StatusCode.IsGood(status))
                throw new Exception(string.Format("Invalid response from the server. (Response Status: {0})", status));
        }

        /// <summary>
        /// 一维数组转换，应该没二维了， 有的话继续扩展，value为字符串，逗号隔开
        /// </summary>
        /// <param name="value"></param>
        /// <param name="builtInType"></param>
        /// <returns></returns>
        private dynamic GetArrayValueFromString(string value, BuiltInType builtInType)
        {
            string[] values = value.Split(',');
            switch (builtInType)
            {
                case BuiltInType.Boolean:
                    {
                        bool[] result = new bool[values.Length];
                        for (int i = 0; i < values.Length; i++)
                        {
                            result[i] = bool.Parse(values[i]);
                        }
                        return result;
                    }
                case BuiltInType.Byte:
                    {
                        byte[] result = new byte[values.Length];
                        for (int i = 0; i < values.Length; i++)
                        {
                            result[i] = byte.Parse(values[i]);
                        }
                        return result;
                    }
                case BuiltInType.DateTime:
                    {
                        DateTime[] result = new DateTime[values.Length];
                        for (int i = 0; i < values.Length; i++)
                        {
                            result[i] = DateTime.Parse(values[i]);
                        }
                        return result;
                    }
                case BuiltInType.Double:
                    {
                        double[] result = new double[values.Length];
                        for (int i = 0; i < values.Length; i++)
                        {
                            result[i] = double.Parse(values[i]);
                        }
                        return result;
                    }
                case BuiltInType.Float:
                    {
                        float[] result = new float[values.Length];
                        for (int i = 0; i < values.Length; i++)
                        {
                            result[i] = float.Parse(values[i]);
                        }
                        return result;
                    }
                case BuiltInType.Guid:
                    {
                        Guid[] result = new Guid[values.Length];
                        for (int i = 0; i < values.Length; i++)
                        {
                            result[i] = Guid.Parse(values[i]);
                        }
                        return result;
                    }
                case BuiltInType.Int16:
                    {
                        short[] result = new short[values.Length];
                        for (int i = 0; i < values.Length; i++)
                        {
                            result[i] = short.Parse(values[i]);
                        }
                        return result;
                    }
                case BuiltInType.Int32:
                    {
                        int[] result = new int[values.Length];
                        for (int i = 0; i < values.Length; i++)
                        {
                            result[i] = int.Parse(values[i]);
                        }
                        return result;
                    }
                case BuiltInType.Int64:
                    {
                        long[] result = new long[values.Length];
                        for (int i = 0; i < values.Length; i++)
                        {
                            result[i] = long.Parse(values[i]);
                        }
                        return result;
                    }
                case BuiltInType.Integer:
                    {
                        int[] result = new int[values.Length];
                        for (int i = 0; i < values.Length; i++)
                        {
                            result[i] = int.Parse(values[i]);
                        }
                        return result;
                    }
                case BuiltInType.LocalizedText:
                    {
                        return values.ToArray();
                    }
                case BuiltInType.SByte:
                    {
                        sbyte[] result = new sbyte[values.Length];
                        for (int i = 0; i < values.Length; i++)
                        {
                            result[i] = sbyte.Parse(values[i]);
                        }
                        return result;
                    }
                case BuiltInType.String:
                    {
                        return values.ToArray();
                    }
                case BuiltInType.UInt16:
                    {
                        ushort[] result = new ushort[values.Length];
                        for (int i = 0; i < values.Length; i++)
                        {
                            result[i] = ushort.Parse(values[i]);
                        }
                        return result;
                    }
                case BuiltInType.UInt32:
                    {
                        uint[] result = new uint[values.Length];
                        for (int i = 0; i < values.Length; i++)
                        {
                            result[i] = uint.Parse(values[i]);
                        }
                        return result;
                    }
                case BuiltInType.UInt64:
                    {
                        ulong[] result = new ulong[values.Length];
                        for (int i = 0; i < values.Length; i++)
                        {
                            result[i] = ulong.Parse(values[i]);
                        }
                        return result;
                    }
                case BuiltInType.UInteger:
                    {
                        uint[] result = new uint[values.Length];
                        for (int i = 0; i < values.Length; i++)
                        {
                            result[i] = uint.Parse(values[i]);
                        }
                        return result;
                    }
                default: throw new Exception("Not supported data type");
            }
        }

        /// <summary>
        /// 单个写入时转换正确的格式
        /// </summary>
        /// <param name="value"></param>
        /// <param name="datatype"></param>
        /// <returns></returns>
        private dynamic GetValueFromString(string value, BuiltInType datatype)
        {
            switch (datatype)
            {
                case BuiltInType.Boolean:
                    {
                        return bool.Parse(value);
                    }
                case BuiltInType.Byte:
                    {
                        return byte.Parse(value);
                    }
                case BuiltInType.DateTime:
                    {
                        return DateTime.Parse(value);
                    }
                case BuiltInType.Double:
                    {
                        return double.Parse(value);
                    }
                case BuiltInType.Float:
                    {
                        return float.Parse(value);
                    }
                case BuiltInType.Guid:
                    {
                        return Guid.Parse(value);
                    }
                case BuiltInType.Int16:
                    {
                        return short.Parse(value);
                    }
                case BuiltInType.Int32:
                    {
                        return int.Parse(value);
                    }
                case BuiltInType.Int64:
                    {
                        return long.Parse(value);
                    }
                case BuiltInType.Integer:
                    {
                        return int.Parse(value);
                    }
                case BuiltInType.LocalizedText:
                    {
                        return value;
                    }
                case BuiltInType.SByte:
                    {
                        return sbyte.Parse(value);
                    }
                case BuiltInType.String:
                    {
                        return value;
                    }
                case BuiltInType.UInt16:
                    {
                        return ushort.Parse(value);
                    }
                case BuiltInType.UInt32:
                    {
                        return uint.Parse(value);
                    }
                case BuiltInType.UInt64:
                    {
                        return ulong.Parse(value);
                    }
                case BuiltInType.UInteger:
                    {
                        return uint.Parse(value);
                    }
                default: throw new Exception("Not supported data type");
            }
        }

        #endregion Node Write/Read Support

        #region DeleteNode Support

        /// <summary>
        /// 删除一个节点的操作，除非服务器配置允许，否则引发异常，成功返回<c>True</c>，否则返回<c>False</c>
        /// </summary>
        /// <param name="tag">节点文本描述</param>
        /// <returns>是否删除成功</returns>
        public bool DeleteExsistNode(string tag)
        {
            DeleteNodesItemCollection waitDelete = new DeleteNodesItemCollection();

            DeleteNodesItem nodesItem = new DeleteNodesItem()
            {
                NodeId = new NodeId(tag),
            };

            m_session.DeleteNodes(
                null,
                waitDelete,
                out StatusCodeCollection results,
                out DiagnosticInfoCollection diagnosticInfos);

            ClientBase.ValidateResponse(results, waitDelete);
            ClientBase.ValidateDiagnosticInfos(diagnosticInfos, waitDelete);

            return !StatusCode.IsBad(results[0]);
        }

        #endregion DeleteNode Support

        #region Test Function

        /// <summary>
        /// 新增一个节点数据
        /// </summary>
        /// <param name="parent">父节点tag名称</param>
        [Obsolete("还未经过测试，无法使用")]
        public void AddNewNode(NodeId parent)
        {
            // Create a Variable node.
            AddNodesItem node2 = new AddNodesItem();
            node2.ParentNodeId = new NodeId(parent);
            node2.ReferenceTypeId = ReferenceTypes.HasComponent;
            node2.RequestedNewNodeId = null;
            node2.BrowseName = new QualifiedName("DataVariable1");
            node2.NodeClass = NodeClass.Variable;
            node2.NodeAttributes = null;
            node2.TypeDefinition = VariableTypeIds.BaseDataVariableType;

            //specify node attributes.
            VariableAttributes node2Attribtues = new VariableAttributes();
            node2Attribtues.DisplayName = "DataVariable1";
            node2Attribtues.Description = "DataVariable1 Description";
            node2Attribtues.Value = new Variant(123);
            node2Attribtues.DataType = (uint)BuiltInType.Int32;
            node2Attribtues.ValueRank = ValueRanks.Scalar;
            node2Attribtues.ArrayDimensions = new UInt32Collection();
            node2Attribtues.AccessLevel = AccessLevels.CurrentReadOrWrite;
            node2Attribtues.UserAccessLevel = AccessLevels.CurrentReadOrWrite;
            node2Attribtues.MinimumSamplingInterval = 0;
            node2Attribtues.Historizing = false;
            node2Attribtues.WriteMask = (uint)AttributeWriteMask.None;
            node2Attribtues.UserWriteMask = (uint)AttributeWriteMask.None;
            node2Attribtues.SpecifiedAttributes = (uint)NodeAttributesMask.All;

            node2.NodeAttributes = new ExtensionObject(node2Attribtues);

            AddNodesItemCollection nodesToAdd = new AddNodesItemCollection { node2 };

            m_session.AddNodes(
                null,
                nodesToAdd,
                out AddNodesResultCollection results,
                out DiagnosticInfoCollection diagnosticInfos);

            ClientBase.ValidateResponse(results, nodesToAdd);
            ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToAdd);
        }

        #endregion Test Function

        #region Monitor Support

        /// <summary>
        /// 新增一批订阅，需要指定订阅的关键字，订阅的tag名数组，以及回调方法
        /// </summary>
        /// <param name="key">关键字</param>
        /// <param name="tags">节点名称数组</param>
        /// <param name="callback">回调方法</param>
        public bool AddAbnormitySubscriptionForKeyGroup(object seesion, string key, string[] tags, Action<object, string, MonitoredItem, MonitoredItemNotificationEventArgs> callback)
        {
            if (m_session == null || m_session.Connected == false)
            {
                UpdateStatus(false, DateTime.UtcNow, "Session not connected!");
                return false;
            }

            try
            {
                Subscription m_subscription = new Subscription(m_session.DefaultSubscription);
                m_subscription.DisplayName = key;
                m_subscription.PublishingEnabled = true;
                m_subscription.PublishingInterval = 3000;
                m_session.AddSubscription(m_subscription);
                // Create the subscription on Server side
                m_subscription.Create();
                // UpdateStatus(true, DateTime.UtcNow, "New Subscription created with SubscriptionId = {0}.", m_subscription.Id);
                // Create MonitoredItems for data changes
                for (int i = 0; i < tags.Length; i++)
                {
                    MonitoredItem intMonitoredItem = new MonitoredItem(m_subscription.DefaultItem);
                    intMonitoredItem.StartNodeId = new NodeId(tags[i]);
                    intMonitoredItem.AttributeId = Attributes.Value;
                    intMonitoredItem.DisplayName = tags[i];
                    intMonitoredItem.SamplingInterval = 3000;
                    intMonitoredItem.Notification += (MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs args) =>
                    {
                        callback?.Invoke(seesion, key, monitoredItem, args);
                    };
                    m_subscription.AddItem(intMonitoredItem);
                }
                m_subscription.ApplyChanges();
                lock (dic_subscriptions)
                {
                    if (dic_subscriptions.ContainsKey(key))
                    {
                        // remove
                        dic_subscriptions[key].Delete(true);
                        m_session.RemoveSubscription(dic_subscriptions[key]);
                        dic_subscriptions[key].Dispose();
                        dic_subscriptions[key] = m_subscription;
                    }
                    else
                    {
                        dic_subscriptions.Add(key, m_subscription);
                    }
                }
                return true;
            }
            catch (Exception ex)
            {
                UpdateStatus(false, DateTime.UtcNow, "Subscribe Nodes Error : {0}.", ex.Message);
                return false;
            }
        }

        /// <summary>
        /// 新增一个订阅，需要指定订阅的关键字，订阅的tag名，以及回调方法
        /// </summary>
        /// <param name="key">关键字</param>
        /// <param name="tag">tag</param>
        /// <param name="callback">回调方法</param>
        public void AddSubscription(object session, string key, string[] tags, Action<object, string, MonitoredItem, MonitoredItemNotificationEventArgs> callback)
        {
            if (m_session == null || m_session.Connected == false)
            {
                UpdateStatus(false, DateTime.UtcNow, "Session not connected!");
                return;
            }

            try
            {
                RemoveAllSubscription();
                Subscription m_subscription = new Subscription(m_session.DefaultSubscription);
                m_subscription.DisplayName = key;
                m_subscription.PublishingEnabled = true;
                m_subscription.PublishingInterval = 100;
                m_session.AddSubscription(m_subscription);
                // Create the subscription on Server side
                m_subscription.Create();
                // UpdateStatus(true, DateTime.UtcNow, "New Subscription created with SubscriptionId = {0}.", m_subscription.Id);
                // Create MonitoredItems for data changes
                for (int i = 0; i < tags.Length; i++)
                {
                    MonitoredItem intMonitoredItem = new MonitoredItem(m_subscription.DefaultItem);
                    intMonitoredItem.StartNodeId = new NodeId(tags[i]);
                    intMonitoredItem.AttributeId = Attributes.Value;
                    intMonitoredItem.DisplayName = tags[i];
                    intMonitoredItem.SamplingInterval = 100;
                    intMonitoredItem.Notification += (MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs args) =>
                    {
                        callback?.Invoke(session, key, monitoredItem, args);
                    };
                    m_subscription.AddItem(intMonitoredItem);
                }
                m_subscription.ApplyChanges();
                lock (dic_subscriptions)
                {
                    if (dic_subscriptions.ContainsKey(key))
                    {
                        // remove
                        dic_subscriptions[key].Delete(true);
                        m_session.RemoveSubscription(dic_subscriptions[key]);
                        dic_subscriptions[key].Dispose();
                        dic_subscriptions[key] = m_subscription;
                    }
                    else
                    {
                        dic_subscriptions.Add(key, m_subscription);
                    }
                }
            }
            catch (Exception ex)
            {
                UpdateStatus(false, DateTime.UtcNow, "Subscribe Nodes Error : {0}.", ex.Message);
            }
        }

        internal void ReadHistoryRawDataValues(string nodeId, DateTime startTime, DateTime endTime, int cou, bool containBound)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 新增一个订阅，需要指定订阅的关键字，订阅的tag名，以及回调方法
        /// </summary>
        /// <param name="key">关键字</param>
        /// <param name="tag">tag</param>
        /// <param name="callback">回调方法</param>
        public void AddSubscription(object session, string key, string tag, int ProcType, int Interval, Action<object, string, int, MonitoredItem, MonitoredItemNotificationEventArgs> callback)
        {
            AddSubscription(session, key, new string[] { tag }, ProcType, Interval, callback);
        }

        /// <summary>
        /// 新增一批订阅，需要指定订阅的关键字，订阅的tag名数组，以及回调方法
        /// </summary>
        /// <param name="key">关键字</param>
        /// <param name="tags">节点名称数组</param>
        /// <param name="callback">回调方法</param>
        public void AddSubscription(object seesion, string key, string[] tags, int ProcType, int Interval, Action<object, string, int, MonitoredItem, MonitoredItemNotificationEventArgs> callback)
        {
            if (m_session == null || m_session.Connected == false)
            {
                UpdateStatus(false, DateTime.UtcNow, "Session not connected!");
                return;
            }

            try
            {
                RemoveAllSubscription();
                Subscription m_subscription = new Subscription(m_session.DefaultSubscription);
                m_subscription.DisplayName = key;
                m_subscription.PublishingEnabled = true;
                m_subscription.PublishingInterval = Interval;
                m_session.AddSubscription(m_subscription);
                // Create the subscription on Server side
                m_subscription.Create();
                // UpdateStatus(true, DateTime.UtcNow, "New Subscription created with SubscriptionId = {0}.", m_subscription.Id);
                // Create MonitoredItems for data changes
                for (int i = 0; i < tags.Length; i++)
                {
                    MonitoredItem intMonitoredItem = new MonitoredItem(m_subscription.DefaultItem);
                    intMonitoredItem.StartNodeId = new NodeId(tags[i]);
                    intMonitoredItem.AttributeId = Attributes.Value;
                    intMonitoredItem.DisplayName = tags[i];
                    intMonitoredItem.SamplingInterval = Interval;
                    intMonitoredItem.Notification += (MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs args) =>
                    {
                        callback?.Invoke(seesion, key, ProcType, monitoredItem, args);
                    };
                    m_subscription.AddItem(intMonitoredItem);
                }
                m_subscription.ApplyChanges();
                lock (dic_subscriptions)
                {
                    if (dic_subscriptions.ContainsKey(key))
                    {
                        // remove
                        dic_subscriptions[key].Delete(true);
                        m_session.RemoveSubscription(dic_subscriptions[key]);
                        dic_subscriptions[key].Dispose();
                        dic_subscriptions[key] = m_subscription;
                    }
                    else
                    {
                        dic_subscriptions.Add(key, m_subscription);
                    }
                }
            }
            catch (Exception ex)
            {
                UpdateStatus(false, DateTime.UtcNow, "Subscribe Nodes Error : {0}.", ex.Message);
            }
        }

        /// <summary>
        /// 新增一批订阅，需要指定订阅的关键字，订阅的tag名数组，以及回调方法
        /// </summary>
        /// <param name="key">关键字</param>
        /// <param name="tags">节点名称数组</param>
        /// <param name="callback">回调方法</param>
        public bool AddSubscriptionForKeyGroup(object seesion, string key, string[] tags, int Proctype, int Inverval, Action<object, string, int, MonitoredItem, MonitoredItemNotificationEventArgs> callback)
        {
            if (m_session == null || m_session.Connected == false)
            {
                UpdateStatus(false, DateTime.UtcNow, "Session not connected!");
                return false;
            }

            try
            {
                Subscription m_subscription = new Subscription(m_session.DefaultSubscription);
                m_subscription.DisplayName = key;
                m_subscription.PublishingEnabled = true;
                m_subscription.PublishingInterval = Inverval;
                m_session.AddSubscription(m_subscription);
                // Create the subscription on Server side
                m_subscription.Create();
                // UpdateStatus(true, DateTime.UtcNow, "New Subscription created with SubscriptionId = {0}.", m_subscription.Id);
                // Create MonitoredItems for data changes
                for (int i = 0; i < tags.Length; i++)
                {
                    MonitoredItem intMonitoredItem = new MonitoredItem(m_subscription.DefaultItem);
                    intMonitoredItem.StartNodeId = new NodeId(tags[i]);
                    intMonitoredItem.AttributeId = Attributes.Value;
                    intMonitoredItem.DisplayName = tags[i];
                    intMonitoredItem.SamplingInterval = Inverval;
                    intMonitoredItem.Notification += (MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs args) =>
                    {
                        callback?.Invoke(seesion, key, Proctype, monitoredItem, args);
                    };
                    m_subscription.AddItem(intMonitoredItem);
                }
                m_subscription.ApplyChanges();
                lock (dic_subscriptions)
                {
                    if (dic_subscriptions.ContainsKey(key))
                    {
                        // remove
                        dic_subscriptions[key].Delete(true);
                        m_session.RemoveSubscription(dic_subscriptions[key]);
                        dic_subscriptions[key].Dispose();
                        dic_subscriptions[key] = m_subscription;
                    }
                    else
                    {
                        dic_subscriptions.Add(key, m_subscription);
                    }
                }
                return true;
            }
            catch (Exception ex)
            {
                UpdateStatus(false, DateTime.UtcNow, "Subscribe Nodes Error : {0}.", ex.Message);
                return false;
            }
        }

        /// <summary>
        /// 移除所有的订阅消息
        /// </summary>
        public void RemoveAllSubscription()
        {
            lock (dic_subscriptions)
            {
                foreach (var item in dic_subscriptions)
                {
                    item.Value.Delete(true);
                    m_session.RemoveSubscription(item.Value);
                    item.Value.Dispose();
                }
                dic_subscriptions.Clear();
            }
        }

        /// <summary>
        /// 移除订阅消息，如果该订阅消息是批量的，也直接移除
        /// </summary>
        /// <param name="key">订阅关键值</param>
        public void RemoveSubscription(string key)
        {
            lock (dic_subscriptions)
            {
                if (dic_subscriptions.ContainsKey(key))
                {
                    // remove
                    dic_subscriptions[key].Delete(true);
                    m_session.RemoveSubscription(dic_subscriptions[key]);
                    dic_subscriptions[key].Dispose();
                    dic_subscriptions.Remove(key);
                }
            }
        }

        #endregion Monitor Support

        #region ReadHistory Support

        /// <summary>
        /// read History data
        /// </summary>
        /// <param name="tag">节点的索引</param>
        /// <param name="start">开始时间</param>
        /// <param name="end">结束时间</param>
        /// <param name="count">读取的个数</param>
        /// <param name="containBound">是否包含边界</param>
        /// <returns>读取的数据列表</returns>
        public IEnumerable<DataValue> ReadHistoryRawDataValues(string tag, DateTime start, DateTime end, uint count = 1, bool containBound = false)
        {
            HistoryReadValueId m_nodeToContinue = new HistoryReadValueId()
            {
                NodeId = new NodeId(tag),
            };

            ReadRawModifiedDetails m_details = new ReadRawModifiedDetails
            {
                StartTime = start,
                EndTime = end,
                NumValuesPerNode = count,
                IsReadModified = false,
                ReturnBounds = containBound
            };

            HistoryReadValueIdCollection nodesToRead = new HistoryReadValueIdCollection();
            nodesToRead.Add(m_nodeToContinue);

            m_session.HistoryRead(
                null,
                new ExtensionObject(m_details),
                TimestampsToReturn.Both,
                false,
                nodesToRead,
                out HistoryReadResultCollection results,
                out DiagnosticInfoCollection diagnosticInfos);

            ClientBase.ValidateResponse(results, nodesToRead);
            ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToRead);

            if (StatusCode.IsBad(results[0].StatusCode))
            {
                throw new ServiceResultException(results[0].StatusCode);
            }

            HistoryData values = ExtensionObject.ToEncodeable(results[0].HistoryData) as HistoryData;
            foreach (var value in values.DataValues)
            {
                yield return value;
            }
        }

        /// <summary>
        /// 读取一连串的历史数据，并将其转化成指定的类型
        /// </summary>
        /// <param name="tag">节点的索引</param>
        /// <param name="start">开始时间</param>
        /// <param name="end">结束时间</param>
        /// <param name="count">读取的个数</param>
        /// <param name="containBound">是否包含边界</param>
        /// <returns>读取的数据列表</returns>
        public IEnumerable<T> ReadHistoryRawDataValues<T>(string tag, DateTime start, DateTime end, uint count = 1, bool containBound = false)
        {
            HistoryReadValueId m_nodeToContinue = new HistoryReadValueId()
            {
                NodeId = new NodeId(tag),
            };

            ReadRawModifiedDetails m_details = new ReadRawModifiedDetails
            {
                StartTime = start.ToUniversalTime(),
                EndTime = end.ToUniversalTime(),
                NumValuesPerNode = count,
                IsReadModified = false,
                ReturnBounds = containBound
            };

            HistoryReadValueIdCollection nodesToRead = new HistoryReadValueIdCollection();
            nodesToRead.Add(m_nodeToContinue);

            m_session.HistoryRead(
                null,
                new ExtensionObject(m_details),
                TimestampsToReturn.Both,
                false,
                nodesToRead,
                out HistoryReadResultCollection results,
                out DiagnosticInfoCollection diagnosticInfos);

            ClientBase.ValidateResponse(results, nodesToRead);
            ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToRead);

            if (StatusCode.IsBad(results[0].StatusCode))
            {
                throw new ServiceResultException(results[0].StatusCode);
            }

            HistoryData values = ExtensionObject.ToEncodeable(results[0].HistoryData) as HistoryData;
            foreach (var value in values.DataValues)
            {
                yield return (T)value.Value;
            }
        }

        #endregion ReadHistory Support

        #region BrowseNode Support

        /// <summary>
        /// 浏览一个节点的引用
        /// </summary>
        /// <param name="tag">节点值</param>
        /// <returns>引用节点描述</returns>
        public ReferenceDescription[] BrowseNodeReference(string tag)
        {
            NodeId sourceId = new NodeId(tag);

            // 该节点可以读取到方法
            BrowseDescription nodeToBrowse1 = new BrowseDescription();

            nodeToBrowse1.NodeId = sourceId;
            nodeToBrowse1.BrowseDirection = BrowseDirection.Forward;
            nodeToBrowse1.ReferenceTypeId = ReferenceTypeIds.Aggregates;
            nodeToBrowse1.IncludeSubtypes = true;
            nodeToBrowse1.NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable | NodeClass.Method);
            nodeToBrowse1.ResultMask = (uint)BrowseResultMask.All;

            // 该节点无论怎么样都读取不到方法
            // find all nodes organized by the node.
            BrowseDescription nodeToBrowse2 = new BrowseDescription();

            nodeToBrowse2.NodeId = sourceId;
            nodeToBrowse2.BrowseDirection = BrowseDirection.Forward;
            nodeToBrowse2.ReferenceTypeId = ReferenceTypeIds.Organizes;
            nodeToBrowse2.IncludeSubtypes = true;
            nodeToBrowse2.NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable);
            nodeToBrowse2.ResultMask = (uint)BrowseResultMask.All;

            BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection();
            nodesToBrowse.Add(nodeToBrowse1);
            nodesToBrowse.Add(nodeToBrowse2);

            // fetch references from the server.
            ReferenceDescriptionCollection references = FormUtils.Browse(m_session, nodesToBrowse, false);

            return references.ToArray();
        }

        #endregion BrowseNode Support

        #region Read Attributes Support

        /// <summary>
        /// 读取一个节点的所有属性
        /// </summary>
        /// <param name="tag">节点信息</param>
        /// <returns>节点的特性值</returns>
        public OpcNodeAttribute[] ReadNoteAttributes(string tag)
        {
            NodeId sourceId = new NodeId(tag);
            ReadValueIdCollection nodesToRead = new ReadValueIdCollection();

            // attempt to read all possible attributes.
            // 尝试着去读取所有可能的特性
            for (uint ii = Attributes.NodeClass; ii <= Attributes.UserExecutable; ii++)
            {
                ReadValueId nodeToRead = new ReadValueId();
                nodeToRead.NodeId = sourceId;
                nodeToRead.AttributeId = ii;
                nodesToRead.Add(nodeToRead);
            }

            int startOfProperties = nodesToRead.Count;

            // find all of the pror of the node.
            BrowseDescription nodeToBrowse1 = new BrowseDescription();

            nodeToBrowse1.NodeId = sourceId;
            nodeToBrowse1.BrowseDirection = BrowseDirection.Forward;
            nodeToBrowse1.ReferenceTypeId = ReferenceTypeIds.HasProperty;
            nodeToBrowse1.IncludeSubtypes = true;
            nodeToBrowse1.NodeClassMask = 0;
            nodeToBrowse1.ResultMask = (uint)BrowseResultMask.All;

            BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection();
            nodesToBrowse.Add(nodeToBrowse1);

            // fetch property references from the server.
            ReferenceDescriptionCollection references = FormUtils.Browse(m_session, nodesToBrowse, false);

            if (references == null)
            {
                return new OpcNodeAttribute[0];
            }

            for (int ii = 0; ii < references.Count; ii++)
            {
                // ignore external references.
                if (references[ii].NodeId.IsAbsolute)
                {
                    continue;
                }

                ReadValueId nodeToRead = new ReadValueId();
                nodeToRead.NodeId = (NodeId)references[ii].NodeId;
                nodeToRead.AttributeId = Attributes.Value;
                nodesToRead.Add(nodeToRead);
            }

            // read all values.
            DataValueCollection results = null;
            DiagnosticInfoCollection diagnosticInfos = null;

            m_session.Read(
                null,
                0,
                TimestampsToReturn.Neither,
                nodesToRead,
                out results,
                out diagnosticInfos);

            ClientBase.ValidateResponse(results, nodesToRead);
            ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToRead);

            // process results.

            List<OpcNodeAttribute> nodeAttribute = new List<OpcNodeAttribute>();
            for (int ii = 0; ii < results.Count; ii++)
            {
                OpcNodeAttribute item = new OpcNodeAttribute();

                // process attribute value.
                if (ii < startOfProperties)
                {
                    // ignore attributes which are invalid for the node.
                    if (results[ii].StatusCode == StatusCodes.BadAttributeIdInvalid)
                    {
                        continue;
                    }

                    // get the name of the attribute.
                    item.Name = Attributes.GetBrowseName(nodesToRead[ii].AttributeId);

                    // display any unexpected error.
                    if (StatusCode.IsBad(results[ii].StatusCode))
                    {
                        item.Type = Utils.Format("{0}", Attributes.GetDataTypeId(nodesToRead[ii].AttributeId));
                        item.Value = Utils.Format("{0}", results[ii].StatusCode);
                    }

                    // display the value.
                    else
                    {
                        TypeInfo typeInfo = TypeInfo.Construct(results[ii].Value);

                        item.Type = typeInfo.BuiltInType.ToString();

                        if (typeInfo.ValueRank >= ValueRanks.OneOrMoreDimensions)
                        {
                            item.Type += "[]";
                        }

                        item.Value = results[ii].Value;//Utils.Format("{0}", results[ii].Value);
                    }
                }

                // process property value.
                else
                {
                    // ignore properties which are invalid for the node.
                    if (results[ii].StatusCode == StatusCodes.BadNodeIdUnknown)
                    {
                        continue;
                    }

                    // get the name of the property.
                    item.Name = Utils.Format("{0}", references[ii - startOfProperties]);

                    // display any unexpected error.
                    if (StatusCode.IsBad(results[ii].StatusCode))
                    {
                        item.Type = String.Empty;
                        item.Value = Utils.Format("{0}", results[ii].StatusCode);
                    }

                    // display the value.
                    else
                    {
                        TypeInfo typeInfo = TypeInfo.Construct(results[ii].Value);

                        item.Type = typeInfo.BuiltInType.ToString();

                        if (typeInfo.ValueRank >= ValueRanks.OneOrMoreDimensions)
                        {
                            item.Type += "[]";
                        }

                        item.Value = results[ii].Value; //Utils.Format("{0}", results[ii].Value);
                    }
                }

                nodeAttribute.Add(item);
            }

            return nodeAttribute.ToArray();
        }

        /// <summary>
        /// 读取一个节点的所有属性
        /// </summary>
        /// <param name="tag">节点值</param>
        /// <returns>所有的数据</returns>
        public DataValue[] ReadNoteDataValueAttributes(string tag)
        {
            NodeId sourceId = new NodeId(tag);
            ReadValueIdCollection nodesToRead = new ReadValueIdCollection();

            // attempt to read all possible attributes.
            // 尝试着去读取所有可能的特性
            for (uint ii = Attributes.NodeId; ii <= Attributes.UserExecutable; ii++)
            {
                ReadValueId nodeToRead = new ReadValueId();
                nodeToRead.NodeId = sourceId;
                nodeToRead.AttributeId = ii;
                nodesToRead.Add(nodeToRead);
            }

            int startOfProperties = nodesToRead.Count;

            // find all of the pror of the node.
            BrowseDescription nodeToBrowse1 = new BrowseDescription();

            nodeToBrowse1.NodeId = sourceId;
            nodeToBrowse1.BrowseDirection = BrowseDirection.Forward;
            nodeToBrowse1.ReferenceTypeId = ReferenceTypeIds.HasProperty;
            nodeToBrowse1.IncludeSubtypes = true;
            nodeToBrowse1.NodeClassMask = 0;
            nodeToBrowse1.ResultMask = (uint)BrowseResultMask.All;

            BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection();
            nodesToBrowse.Add(nodeToBrowse1);

            // fetch property references from the server.
            ReferenceDescriptionCollection references = FormUtils.Browse(m_session, nodesToBrowse, false);

            if (references == null)
            {
                return new DataValue[0];
            }

            for (int ii = 0; ii < references.Count; ii++)
            {
                // ignore external references.
                if (references[ii].NodeId.IsAbsolute)
                {
                    continue;
                }

                ReadValueId nodeToRead = new ReadValueId();
                nodeToRead.NodeId = (NodeId)references[ii].NodeId;
                nodeToRead.AttributeId = Attributes.Value;
                nodesToRead.Add(nodeToRead);
            }

            // read all values.
            DataValueCollection results = null;
            DiagnosticInfoCollection diagnosticInfos = null;

            m_session.Read(
                null,
                0,
                TimestampsToReturn.Neither,
                nodesToRead,
                out results,
                out diagnosticInfos);

            ClientBase.ValidateResponse(results, nodesToRead);
            ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToRead);

            return results.ToArray();
        }

        #endregion Read Attributes Support

        #region Method Call Support

        /// <summary>
        /// call a server method
        /// </summary>
        /// <param name="tagParent">方法的父节点tag</param>
        /// <param name="tag">方法的节点tag</param>
        /// <param name="args">传递的参数</param>
        /// <returns>输出的结果值</returns>
        public object[] CallMethodByNodeId(string tagParent, string tag, params object[] args)
        {
            if (m_session == null)
            {
                return null;
            }

            IList<object> outputArguments = m_session.Call(
                new NodeId(tagParent),
                new NodeId(tag),
                args);

            return outputArguments.ToArray();
        }

        #endregion Method Call Support

        #endregion Public Methods

        #region Private Methods

        /// <summary>
        /// Handles the certificate validation event.
        /// This event is triggered every time an untrusted certificate is received from the server.
        /// </summary>
        private void CertificateValidation(CertificateValidator sender, CertificateValidationEventArgs e)
        {
            bool certificateAccepted = true;

            // ****
            // Implement a custom logic to decide if the certificate should be
            // accepted or not and set certificateAccepted flag accordingly.
            // The certificate can be retrieved from the e.Certificate field
            // ***

            ServiceResult error = e.Error;
            while (error != null)
            {
                m_output.WriteLine(error);
                error = error.InnerResult;
            }

            if (certificateAccepted)
            {
                UpdateStatus(true, DateTime.UtcNow, "Untrusted Certificate accepted. SubjectName = {0}", e.Certificate.SubjectName);
            }

            e.AcceptAll = certificateAccepted;
        }

        /// <summary>
        /// Raises the connect complete event on the main GUI thread.
        /// </summary>
        private void DoConnectComplete(object state)
        {
            m_ConnectComplete?.Invoke(this, null);
        }

        /// <summary>
        /// Handle DataChange notifications from Server
        /// </summary>
        private void OnMonitoredItemNotification(MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs e)
        {
            try
            {
                // Log MonitoredItem Notification event
                MonitoredItemNotification notification = e.NotificationValue as MonitoredItemNotification;
                UpdateStatus(true, DateTime.UtcNow, "Notification Received for Variable \"{0}\" and Value = {1}.", monitoredItem.DisplayName, notification.Value);
            }
            catch (Exception ex)
            {
                UpdateStatus(false, DateTime.UtcNow, "OnMonitoredItemNotification error: {0}", ex.Message);
            }
        }

        /// <summary>
        /// Handles a reconnect event complete from the reconnect handler.
        /// </summary>
        private void Server_ReconnectComplete(object sender, EventArgs e)
        {
            try
            {
                // ignore callbacks from discarded objects.
                if (!Object.ReferenceEquals(sender, m_reConnectHandler))
                {
                    return;
                }

                m_session = (Session)m_reConnectHandler.Session;
                m_reConnectHandler.Dispose();
                m_reConnectHandler = null;

                // raise any additional notifications.
                m_ReconnectComplete?.Invoke(this, e);
            }
            catch (Exception exception)
            {
                UpdateStatus(false, DateTime.UtcNow, "ReconnectComplete Error : {0}.", exception.Message);
            }
        }

        /// <summary>
        /// Handles a keep alive event from a session.
        /// </summary>
        private void Session_KeepAlive(ISession session, KeepAliveEventArgs e)
        {
            try
            {
                // check for events from discarded sessions.
                if (!Object.ReferenceEquals(session, m_session))
                {
                    return;
                }

                // start reconnect sequence on communication error.
                if (ServiceResult.IsBad(e.Status))
                {
                    if (m_reconnectPeriod <= 0)
                    {
                        UpdateStatus(true, e.CurrentTime, "Communication Error ({0})", e.Status);
                        return;
                    }

                    UpdateStatus(true, e.CurrentTime, "Reconnecting in {0}s", m_reconnectPeriod);

                    if (m_reConnectHandler == null)
                    {
                        m_ReconnectStarting?.Invoke(this, e);

                        m_reConnectHandler = new SessionReconnectHandler();
                        m_reConnectHandler.BeginReconnect(m_session, m_reconnectPeriod * 1000, Server_ReconnectComplete);
                    }

                    return;
                }

                // update status.
                //UpdateStatus(true, e.CurrentTime, "Connected [{0}]", session.Endpoint.EndpointUrl);

                // raise any additional notifications.
                m_KeepAliveComplete?.Invoke(this, e);
            }
            catch (Exception exception)
            {
                UpdateStatus(false, DateTime.UtcNow, "KeepAlive Error : {0}.", exception.Message);
            }
        }

        /// <summary>
        /// Report the client status
        /// </summary>
        /// <param name="error">Whether the status represents an error.</param>
        /// <param name="time">The time associated with the status.</param>
        /// <param name="status">The status message.</param>
        /// <param name="args">Arguments used to format the status message.</param>
        private void UpdateStatus(bool error, DateTime time, string status, params object[] args)
        {
            m_OpcStatusChange?.Invoke(this, new OpcUaStatusEventArgs()
            {
                Error = error,
                Time = time.ToLocalTime(),
                Text = String.Format(status, args),
            });
        }

        #endregion Private Methods

        #region Private Fields

        private readonly IOutput m_output;
        private readonly Action<IList, IList> m_validateResponse;
        private Dictionary<string, Subscription> dic_subscriptions;
        private ApplicationConfiguration m_configuration;
        private EventHandler m_ConnectComplete;
        private EventHandler m_KeepAliveComplete;
        private EventHandler<OpcUaStatusEventArgs> m_OpcStatusChange;
        private EventHandler m_ReconnectComplete;
        private SessionReconnectHandler m_reConnectHandler;
        private EventHandler m_ReconnectStarting;
        private Session m_session;
        // 系统所有的节点信息

        #endregion Private Fields
    }
}
